﻿/**************************************************************************** 
 * Copyright (c) 2011 ECAlford
 * Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 *****************************************************************************/

using System;
using System.Text;
using AceBots.Xbee;

#if MicroFramework
using Microsoft.SPOT;
#endif

namespace AceBots.ARPacket
{
      #region Enums
        public enum ARPacketTypes : byte
        {
            InvalidPacket=0x00,
            ShortCommand=0x01,
            VerboseCommand= 0x02,
            BinaryReadings=0x03,
            BinaryShortReadings=0x04,
            BinarySingleReadType=0x05,
        }

        public enum ARPacketValueTypes : byte
        {
            IsByte = 0x01,
            IsShort = 0x02,  // 16 bit integer
            IsString = 0x03, // note all strings must be null terminated
            IsInt32 = 0x04,  // 32 bit integer
        }
        #endregion
    public class ARPacket
    {
        /********************************************************************
         * The ARPacket provide some simple standards for passing data and commands
         * between Microcontroller applications. Typically used in conjunction
         * with wireless networking.
         * Basic Format is dependent on packet type value (see enum above).
         * Maximum overall packet length defined to be 255 bytes. Since My initial
         * implementation is intended to use XBEE the practical limitation is much shorter
         * (98 bytes of data 100 bytes including packet type and length)
         * 
         * Note in all packet definitions below any time a Ushort or Short data type is specified
         *    the MSB should be sent first and then the LSB.
         * ****
         * Short Command Packet type
         * Packet Type = 0x01 - 
         * Packet Data Length: 1 byte Max packet length = 255 bytes does not count 2 bytes for packet type and length
         * Command ID - UShort Can be Defined per application
         * Parameter 1 - Short (not required but can be defined per application/command)
         * Parameter 2 - Short (not required but can be defined per application/command)
         * ****
         *  Verbose Command Packet type This capability was removed along with associated 
         *  Code below.
         * Packet Type = 0x02
         * Packet Length - byte
         * Command ID Ushort
         * Command Description String (0 terminated)
         * Parameter 1 - Short (not required but can be defined per application/command)
         * Parameter 2 - Short (not required but can be defined per application/command)
         ******
         * Data Readings Packet Binary
         * Packet Type = 0x03 - 
         * Packet Data Length: 1 byte Max packet length = 255 bytes does not count 2 bytes for packet type and length
         *    ------ The reading values are sent ordered triples per:
         * Reading ID 1 - Short This is an application defined ID that identifies the type of reading being passed
         *              It can be used to identify the sensor to which the reading belongs.
         * Reading Value type 1 - byte indicates the type of data being sent see ARPacketValueTypes Enum 
         * Reading Value 1 - variable length dependent upon Reading Value Type
         * Reading ID 2 - UShort This is an application defined ID that identifies the type of reading being passed
         *              It can be used to identify the sensor to which the reading belongs.
         * Reading Value type 2 - byte indicates the type of data being sent see ARPacketValueTypes Enum 
         * Reading Value 2 - variable length dependent upon Reading Value Type
         * ... through Reading Value n
         * 
         * Data Readings Packet Short Binary
         * This is just a series of 4 byte data elements with the two bytes being the reading ID
         *  and the next two bytes being a short reading.
         * Packet Type = 0x04
         *  Packet Data Length: 1 byte Max packet length = 255 bytes does not count 2 bytes for packet type and length
         * Reading ID 1 - Short This is an application defined ID that identifies the type of reading being passed
         *              It can be used to identify the sensor to which the reading belongs.
         * Reading Value 1 - Short reading value
         * Reading ID 2 - UShort This is an application defined ID that identifies the type of reading being passed
         * Reading Value 2 - Short reading value
         * ... through Reading Value n
         * 
         * Data Readings Single ReadType
         *  This stores readings of a single type (single sensor)
         *  Packet Type=0x05;
          * Packet Data Length: 1 byte Max packet length = 255 bytes does not count 2 bytes for packet type and length
         * Reading ID  - Short This is an application defined ID that identifies the type of reading being passed
         *              It can be used to identify the sensor to which the reading belongs.
         * Reading Value type  - byte indicates the type of data being sent see ARPacketValueTypes Enum For
         * Reading 1 
         * Reading 2
         * ... Reading N
         * ***************************************************************************************/
        #region Class Private Vars
        private XbeeV1FrameMgr cFrameMgr;
        private byte cbFrameID;
        private int ciCurrentBufferIndex;
        private byte cbDataPointCount;
        private bool cbAtBufferEnd;
        #endregion

        #region Properties
        public byte ARPacketLength { get; set; }
        public ARPacketTypes PacketType { get; private set; }
        public ushort Command { get; set; }
        public string CommandName {get;set;}
        public short Parameter1 { get; set; }
        public short Parameter2 { get; set; }
        public byte DataPointCount { get { return cbDataPointCount; } }
        public short CurrentReadId {get; set;} 
        public ARPacketValueTypes CurrentValueType {get;private set;}
        public byte CurrentByte{get;private set;}
        public short CurrentShort {get; private set;}
        public Int32 CurrentInt {get; private set;}
        public string CurrentString {get; private set;}
        
        public ushort SourceSystemAddress { get; set; }
        public ushort DestinationSystemAddress { get; set; }

        public bool IsDataPacket
        {
            get
            {
                if (PacketType == ARPacketTypes.BinarySingleReadType ||
                    PacketType == ARPacketTypes.BinaryShortReadings ||
                    PacketType == ARPacketTypes.BinaryReadings)
                    return true;
                else
                    return false;
            }
        }
        
        #endregion

        #region Creates ARPacket
        // note these creates assume we are using an XBee to communicate the packets using
        // API frames. Hence all creates involve an XbeeV1FrameMgr class.
        // the advantage of this approach is that we don't have to add another character buffer
       
        

        // Short Command Packet creates
        public ARPacket(XbeeV1FrameMgr xbFrameMgr, XbeeV1Module xbModule, ARPacketTypes PktType, ushort pCommand)
        {
            cInitializePacket(xbFrameMgr, xbModule, PktType, pCommand);
        }
          public ARPacket(XbeeV1FrameMgr xbFrameMgr, XbeeV1Module xbModule, ARPacketTypes PktType, 
              ushort pCommand, short Param1,short Param2)
        {
            cInitializePacket(xbFrameMgr, xbModule, PktType, pCommand);
            Parameter1 = Param1;
            Parameter2 = Param2;
            cAppendVal(Param1);
            cAppendVal(Param2);
        }
        // Verbose Command Packet creates
          public ARPacket(XbeeV1FrameMgr xbFrameMgr, XbeeV1Module xbModule, ARPacketTypes PktType, ushort pCommand,
              string psCommandName) // 
        {
            cInitializePacket(xbFrameMgr, xbModule, PktType, pCommand);
            cAppendVal(psCommandName);
        }
          public ARPacket(XbeeV1FrameMgr xbFrameMgr, XbeeV1Module xbModule, ARPacketTypes PktType, ushort pCommand,
              string psCommandName, short Param1, short Param2)
          {
              cInitializePacket(xbFrameMgr, xbModule, PktType, pCommand);
              cAppendVal(psCommandName);
              Parameter1 = Param1;
              Parameter2 = Param2;
              cAppendVal(Param1);
              cAppendVal(Param2);
          }
     
        // Binary Data Packet Types
          public ARPacket(XbeeV1FrameMgr xbFrameMgr, XbeeV1Module xbModule,  ARPacketTypes PktType)
          {
              cInitializePacket(xbFrameMgr, xbModule, PktType);
          }
        //Rebuild from XBeeV1FrameMgr
          public ARPacket(XbeeV1FrameMgr xbFrameMgr)
          {
              // This just parses the frame to set the properties
              // Check to see if this is a data frame. If not we don't 
              // know how to handle
              cFrameMgr=xbFrameMgr;
              if (xbFrameMgr.Buffer[(int)XbeeFrameBufferOffsets.ApiIdentityOffset] == (byte)ApiIdentity.ReceiveRequest16)
              {
                  ciCurrentBufferIndex=(int) XbeeFrameBufferOffsets.FrameIdOffsetOrSourceAddress;
                  SourceSystemAddress=(ushort)cParseOutShort();
                  ciCurrentBufferIndex = (int)XbeeFrameBufferOffsets.RFDataPacketOffset;

                  PacketType=(ARPacketTypes) xbFrameMgr.Buffer[ciCurrentBufferIndex++];
                  ARPacketLength = xbFrameMgr.Buffer[ciCurrentBufferIndex++];
                  if(PacketType==ARPacketTypes.ShortCommand || PacketType==ARPacketTypes.VerboseCommand)
                  {

                      Command=(ushort)(((ushort)xbFrameMgr.Buffer[ciCurrentBufferIndex++])<<8);
                      Command+=(ushort)xbFrameMgr.Buffer[ciCurrentBufferIndex++];
                     CommandName=string.Empty;
                      if(PacketType==ARPacketTypes.VerboseCommand)
                      {
                          CommandName= cParseOutString();
                      }
                      Parameter1=cParseOutShort();
                      Parameter2=cParseOutShort();
                  }
                 
                  else if (PacketType==ARPacketTypes.BinaryReadings || PacketType==ARPacketTypes.BinaryShortReadings
                      || PacketType==ARPacketTypes.BinarySingleReadType)
                  {
                      Command=0;
                      Parameter1=0;
                      Parameter2=0;
                      //ARPacketLength=  cFrameMgr.Buffer[ciCurrentBufferIndex++];
                      if  ( PacketType==ARPacketTypes.BinaryShortReadings)
                            cbDataPointCount=(byte)((int)ARPacketLength>>2); //this is just dividing by 4 bytes per reading
                      if ( PacketType==ARPacketTypes.BinarySingleReadType)
                      {
                          CurrentReadId=cParseOutShort();
                          CurrentValueType= (ARPacketValueTypes)cParseOutByte();
                          switch (CurrentValueType)
	                      {
                              case ARPacketValueTypes.IsByte:
                                  cbDataPointCount=(byte)(ARPacketLength-3);
                                  break;
                              case ARPacketValueTypes.IsShort:
                                  cbDataPointCount=(byte)((ARPacketLength-3)>>1); // divide by 2 bytes/read
                                  break;
                              case ARPacketValueTypes.IsInt32:
                                  cbDataPointCount=(byte)((ARPacketLength-3)>>2); // divide by 4 bytes/read
                                  break;

		                   
	                      }
                      }

                      cReset(); // need to reset 
                  }
                  else
                  {
                      cInitializeInvalid();
                  }
              }
              else
              {
                  // dont know why we are here but set everything to 0
                  cInitializeInvalid();
              }
          }


        #endregion

        #region public methods writing to packet
        /********************************************
         * The AppendData methods will add the data to an existing
         * ARPacket of packet type = binary reading type
         ******************/ 
          public bool AppendData(ushort pReadID,byte pbVal)
          {
              if (cFrameMgr.AvailableBufferLen() > ciCurrentBufferIndex + 4)
              {
                  cAppendReadIdValueType(pReadID, ARPacketValueTypes.IsByte);
                  cbDataPointCount++;
                  cFrameMgr.Buffer[ciCurrentBufferIndex++] = pbVal;
                  return true;
              }
              else return false;
          }

         public bool AppendData(ushort pReadID,int pi32Val)
          {
              if (cFrameMgr.AvailableBufferLen() > (ciCurrentBufferIndex +7))
              {
                  cAppendReadIdValueType(pReadID, ARPacketValueTypes.IsInt32);
                  cbDataPointCount++;
                  cAppendVal(pi32Val);
                  return true;
              }
              else return false;
          }

         public bool AppendData(ushort pReadID, short pi16Val)
         {
             if (cFrameMgr.AvailableBufferLen() > (ciCurrentBufferIndex + 5))
             {
                 cAppendReadIdValueType(pReadID, ARPacketValueTypes.IsShort);
                 cbDataPointCount++;
                 cAppendVal((ushort)pi16Val);
                 return true;
             }
             else return false;
         }

          public bool AppendData(ushort pReadID,string psVal)
          {
              if (cFrameMgr.AvailableBufferLen() > (ciCurrentBufferIndex + psVal.Length + 4))
              {
                  cAppendReadIdValueType(pReadID, ARPacketValueTypes.IsString);
                  cbDataPointCount++;
                  cAppendVal(psVal);
                  return true;
              }
              else return false;
          }
        /****************************
         * The CalcCheckSumAndLength Method fills in the length parameters and checksum of the 
         * FrameMgr Packet and the ARPacket
         * ***********/
          public void CalcCheckSumAndLength()
          {
              ARPacketLength=(byte) (ciCurrentBufferIndex -
                 (int) XbeeFrameBufferOffsets.RFDataPacketOffset - 2);
              cFrameMgr.Buffer[(int)XbeeFrameBufferOffsets.RFDataPacketOffset + 1] = ARPacketLength;
              cFrameMgr.FillXbeeDataPacket(cbFrameID,ciCurrentBufferIndex - 3); // The length of an xbee frame data 
              
          }

        #endregion
        
        #region Public Methods Reading From Packet
         public bool GetNextReading(out short piReadID, out ARPacketValueTypes pValueType,
                 out short psVal, out int piVal, out string pstrVal, out byte pbVal)
         {
             // this function returns information about the next reading
             // It returns true if there is data in the packet that has not been retrieved and
             // sets the piReadID, pValuetype and associated psval based on the pValueType param.
             // it returns false if we have reached the end of the packet and there is no data
             // to return.
              cGetNextReading();
            piReadID=CurrentReadId;
            psVal=CurrentShort;
             piVal=CurrentInt;
             pstrVal=CurrentString;
             pValueType=CurrentValueType;
             pbVal=CurrentByte;
            return !cbAtBufferEnd;


         }
        public bool GetNextReading(out short piReadID, out short psvalue)
        {
            // this function returns information about the next reading
             // It returns true if there is data in the packet that has not been retrieved and
             // sets the piReadID, the short reading. This return will only work if the packet type
            // is defined as  BinaryShortReadings or BinarySingleReadType and the readtype isshort
             // it returns false if we have reached the end of the packet and there is no data
             // to return or if the packetype and readtype requirements are not met.
            cGetNextReading();
            piReadID=CurrentReadId;
            psvalue=CurrentShort;
            return !cbAtBufferEnd;

        }


        #endregion
        #region Private methods
        private void cAppendReadIdValueType(ushort pReadID,ARPacketValueTypes pValuetype)
        {
            // Depending upon the packet type we may or may not need
            // to append the readID. 
            switch (PacketType)
	        {
                
                case ARPacketTypes.BinaryReadings:
                    cAppendVal(pReadID);
                    cAppendVal((byte)(pValuetype));
                    break;
                case ARPacketTypes.BinaryShortReadings:
                    cAppendVal(pReadID);
                    break;
                case ARPacketTypes.BinarySingleReadType:
                    if(cbDataPointCount==0)
                    {
                         cAppendVal(pReadID);
                    cAppendVal((byte)(pValuetype));
                    }
                    break;
                default:
                    break;
	        }
            
        }
        private void cAppendVal(ushort pShort)
        {
            // make sure buffer is long enough before appending
            if (cFrameMgr.AvailableBufferLen() > ciCurrentBufferIndex + 2)
            {
                cFrameMgr.Buffer[ciCurrentBufferIndex++] = (byte)(((ushort)pShort) >> 8);
                cFrameMgr.Buffer[ciCurrentBufferIndex++] = (byte)(((ushort)pShort) & 0xFF);
            }

        }
        private void cAppendVal(byte pByte)
        {
            if (cFrameMgr.AvailableBufferLen() > ciCurrentBufferIndex + 2)
            {
                cFrameMgr.Buffer[ciCurrentBufferIndex++] = pByte;
            }
        }
        private void cAppendVal(short pShort)
          {
              // make sure buffer is long enough before appending
              if( cFrameMgr.AvailableBufferLen()> ciCurrentBufferIndex+3)
              {
                  cFrameMgr.Buffer[ciCurrentBufferIndex++] = (byte)(((ushort)pShort) >> 8);
                  cFrameMgr.Buffer[ciCurrentBufferIndex++] = (byte)(((ushort)pShort) & 0xFF); 
              }

          }
          private void cAppendVal(int piVal)
          {
              // make sure buffer is long enough before appending
              if (cFrameMgr.AvailableBufferLen() > ciCurrentBufferIndex + 5)
              {
                  cFrameMgr.Buffer[ciCurrentBufferIndex++] = (byte)(((uint)piVal) >> 24);
                  cFrameMgr.Buffer[ciCurrentBufferIndex++] = (byte)((((uint)piVal) >> 16)& 0xFF);
                  cFrameMgr.Buffer[ciCurrentBufferIndex++] = (byte)((((uint)piVal) >> 8)& 0xFF);
                  cFrameMgr.Buffer[ciCurrentBufferIndex++] = (byte)(((uint)piVal) & 0xFF);
              }

          }
          private void cAppendVal(string pString)
          {
              // make sure buffer is long enough before appending
              if (cFrameMgr.AvailableBufferLen() > ciCurrentBufferIndex+pString.Length+2)
              {
                byte[] bRay=Encoding.UTF8.GetBytes(pString);
                for (int i=0; i < pString.Length; i++)
                {
                    cFrameMgr.Buffer[ciCurrentBufferIndex++] = bRay[i];
                }
                cFrameMgr.Buffer[ciCurrentBufferIndex++] = 0;
              }

          }

          private void cGetNextReading()
        {
               switch (PacketType)
	        {

                case ARPacketTypes.BinaryReadings:
                    CurrentReadId=cParseOutShort();
                    CurrentValueType=(ARPacketValueTypes)cParseOutByte();
               
                 break;
                case ARPacketTypes.BinaryShortReadings:
                    CurrentReadId=cParseOutShort();
                    CurrentValueType = ARPacketValueTypes.IsShort;
                 break;
              

	        }
            switch (CurrentValueType)
	        {
		        case ARPacketValueTypes.IsByte:
                    CurrentByte=cParseOutByte();
                    break;
                case ARPacketValueTypes.IsShort:
                    CurrentShort=cParseOutShort();
                    break;
                case ARPacketValueTypes.IsString:
                    CurrentString=cParseOutString();
                    break;
                case ARPacketValueTypes.IsInt32:
                CurrentInt=cParseOutInt();
                    break;
                      
	        } 
        }
         /**** InitializePacket command set up the xbFrameMgr Packet
          * as far as can be done without final data
          *********/
          private void cInitializePacket(XbeeV1FrameMgr xbFrameMgr, XbeeV1Module xbModule,
            ARPacketTypes PktType, ushort pCommand)
          {
              cInitializePacket(xbFrameMgr, xbModule, PktType);
              cAppendVal(pCommand);
              Command = pCommand;

          }
          private void cInitializePacket(XbeeV1FrameMgr xbFrameMgr, XbeeV1Module xbModule,
             ARPacketTypes PktType)
          {
              cFrameMgr = xbFrameMgr;
              cFrameMgr.Reset();
              xbFrameMgr.ApiID = ApiIdentity.TransmitRequest16;

             cbDataPointCount=0;
             PacketType = PktType;
              cbFrameID = xbModule.GetNextPacketID();
            
              ciCurrentBufferIndex = (int)XbeeFrameBufferOffsets.RFDataPacketOffset;
              cFrameMgr.Buffer[ciCurrentBufferIndex++] = (byte)PktType;
              ciCurrentBufferIndex++; // Skip the packet length byte for now will be filled in later
            

          }
          private void cInitializeInvalid()
	     {
		         cbFrameID=0;
                 ciCurrentBufferIndex=0;
                 cbDataPointCount=0;
                  ARPacketLength=0;
                  Command=0;
                  Parameter1=0;
                  Parameter2=0;
                  PacketType=ARPacketTypes.InvalidPacket; 
	        }

         

        private byte cParseOutByte()
          {
              byte retVal=0;
              if(ciCurrentBufferIndex < (cFrameMgr.DataLength+3))
              {
                  retVal=cFrameMgr.Buffer[ciCurrentBufferIndex++];
                 cbAtBufferEnd=false;
              }
              else cbAtBufferEnd=true;

              return retVal;
          }
        private int cParseOutInt()
        {
              int iRetVal = 0;
            if (ciCurrentBufferIndex < (cFrameMgr.DataLength+6))
            {
                iRetVal =  cFrameMgr.Buffer[ciCurrentBufferIndex++];
                iRetVal = (iRetVal << 8) + cFrameMgr.Buffer[ciCurrentBufferIndex++];
                iRetVal = (iRetVal << 8) + cFrameMgr.Buffer[ciCurrentBufferIndex++];
                iRetVal = (iRetVal << 8) + cFrameMgr.Buffer[ciCurrentBufferIndex++];
                cbAtBufferEnd=false;
            }
            else cbAtBufferEnd=true;
            return iRetVal;
        }
         private short cParseOutShort()
          {
              short retVal=0;
              if(ciCurrentBufferIndex < (cFrameMgr.DataLength+4))
              {
                  retVal=cFrameMgr.Buffer[ciCurrentBufferIndex++];
                  retVal=(short)((retVal<<8)+ (short) cFrameMgr.Buffer[ciCurrentBufferIndex++]);
               cbAtBufferEnd=false;
              }
              else cbAtBufferEnd=true;
              return retVal;
          }
          private string cParseOutString()
          {
           string str = string.Empty;
              if (ciCurrentBufferIndex<cFrameMgr.DataLength+3) cbAtBufferEnd=false;
              else cbAtBufferEnd=true;
           while((ciCurrentBufferIndex < (cFrameMgr.DataLength+3)) && 
               cFrameMgr.Buffer[ciCurrentBufferIndex]!=0)
            
            {
                str += (char)cFrameMgr.Buffer[ciCurrentBufferIndex++];
            }
           ciCurrentBufferIndex++;
            return str;
          }
      
        private void cReset()
        {
            // this function resets to the the index ciCurrentBuffer to the correct 
            // place to read data 
            switch (PacketType)
	        {

                case ARPacketTypes.BinaryReadings:
                    ciCurrentBufferIndex=(int)(XbeeFrameBufferOffsets.RFDataPacketOffset+2);
                   
                 break;
                case ARPacketTypes.BinaryShortReadings:
                    ciCurrentBufferIndex=(int)(XbeeFrameBufferOffsets.RFDataPacketOffset+2);
                 break;
                case ARPacketTypes.BinarySingleReadType:
                    ciCurrentBufferIndex=(int)(XbeeFrameBufferOffsets.RFDataPacketOffset+5);
                 break;

	        }
        }

          #endregion
        #region Obsolete Code
        /************************************************************************************/
        
        #endregion

    }
}
